package world;

import gui.Main;

import java.awt.Point;
import java.util.Vector;

import javax.swing.JTree;
import javax.swing.tree.DefaultMutableTreeNode;
import javax.swing.tree.TreeNode;


import AI.*;

public class Player {
	
	private static KnowledgeBase kb;
	private int arrow;
	private int sandbag;
	private Point position;
	private FieldValues dir;
	private JTree DFSTree;
	private DFSTreeNode currentNode;
	private DFSTreeNode rootNode;
	private Vector<Point> visitedNodes = new Vector<Point>();
	private Vector<Point> allPossibleNodes = new Vector<Point>();
	private boolean coverHole = false;
	
	public Player() { 
		kb= new KnowledgeBase();
		arrow=1; 
		sandbag=1;
		position = new Point(0,0);
		dir = FieldValues.RIGHT;
		rootNode = currentNode = new DFSTreeNode(position, true);
		DFSTree = new JTree(currentNode);
		allPossibleNodes.add(new Point(position));
	}

	public Point getPosition() {
		return new Point(position);
	}
	public FieldValues getDirection() {
		return dir;
	}	
	public int getArrow() {
		return arrow;
	}
	public int getSandbag() {
		return sandbag;
	}

	public void setArrow(int arrow) {
		this.arrow = arrow;
	}	
	public void setPosition(Point p) {
		position = p;
	}	
	public void setDirection(FieldValues f) {
		dir = f;
	}
	public void setSandbag(int sandbag) {
		this.sandbag = sandbag;
	}
	
	public void move() {
		switch (dir) {
		case UP:
			position.translate(0, -1);
			break;
		case DOWN:
			position.translate(0, 1);
			break;
		case RIGHT:
			position.translate(1, 0);
			break;
		case LEFT:
			position.translate(-1, 0);
			break;
		}
	}	
	public void turn(Action a) {
		if (a == Action.TURN_LEFT) {
			switch (dir) {
			case UP:
				setDirection(FieldValues.LEFT);
				break;
			case RIGHT:
				setDirection(FieldValues.UP);
				break;
			case DOWN:
				setDirection(FieldValues.RIGHT);
				break;
			case LEFT:
				setDirection(FieldValues.DOWN);
				break;
			default:
				break;
			}
		}
		else if (a == Action.TURN_RIGHT) {
			switch (dir) {
			case UP:
				setDirection(FieldValues.RIGHT);
				break;
			case RIGHT:
				setDirection(FieldValues.DOWN);
				break;
			case DOWN:
				setDirection(FieldValues.LEFT);
				break;
			case LEFT:
				setDirection(FieldValues.UP);
				break;
			default:
				break;
			}
		}
	}
	
	public FieldValues shoot() {
		arrow = 0;
		return getDirection();
	}	
	public boolean coverHole() {
		if (sandbag >0) {
			sandbag--;
			return true;
		}
		else {
			return false;
		}
	}
	
	public int[] sense(int val, boolean wall, boolean killed) {
		//[stench,breeze,glitter,bump,scream]
		LiteralType[] lt = {LiteralType.STENCH, LiteralType.BREEZE, LiteralType.GOLD};
		int[] sensors = new int[5];		
		for (int i = 0; i < lt.length; i++) {
			if ((val % lt[i].getNumber())==0)
				sensors[i]=1;
			else
				sensors[i]=0;
		}
		if (wall == true)
			sensors[3] = 1;
		else
			sensors[3] = 0;
		if (killed == true)
			sensors[4] = 1;
		else
			sensors[4] = 0;
		
		return sensors;
	}
	
	public void tellKB(int[] sensors) {
		Formula f = new Formula();
		//TODO kasowanie powinno być f.setLiteral(new Literal(getPosition(),LiteralType.AGENT), 0);
		kb.visitedField(getPosition());
		for (int i=0; i<5; ++i) {
			switch (i) {
			case 0:
				if (sensors[i]==1) {
					f.setLiteral(new Literal(getPosition(),LiteralType.STENCH), 0);
					Point p = getPosition();
					kb.isDanger(new Point(p.x, p.y-1), LiteralType.WUMPUS);
					kb.isDanger(new Point(p.x+1, p.y), LiteralType.WUMPUS);
					kb.isDanger(new Point(p.x, p.y+1), LiteralType.WUMPUS);
					kb.isDanger(new Point(p.x-1, p.y), LiteralType.WUMPUS);
				} else {
					f.setLiteral(new Literal(getPosition(),LiteralType.NO_STENCH), 0);
					kb.noSignal(getPosition(), LiteralType.NO_STENCH);
				}
				break;
			case 1:
				if (sensors[i]==1) {
					f.setLiteral(new Literal(getPosition(),LiteralType.BREEZE), 0);
				} else {
					f.setLiteral(new Literal(getPosition(),LiteralType.NO_BREEZE), 0);
					kb.noSignal(getPosition(), LiteralType.NO_BREEZE);
				}
				break;
			case 2:
				if (sensors[i]==1) {
					f.setLiteral(new Literal(getPosition(),LiteralType.GLITTER), 0);
					kb.isGold(getPosition(),true);
				} else {
					f.setLiteral(new Literal(getPosition(),LiteralType.NO_GLITTER), 0);
					kb.isGold(getPosition(), false);					
				}
				break;
			case 3:
				if (sensors[i]==1) {
					f.setLiteral(new Literal(getPosition(),LiteralType.BUMP_WALL), 0);
					kb.wallEdge(getPosition(), getDirection());
				} else {
					kb.noBumpWall(getPosition());
				}
				break;
			case 4:
				if (sensors[i]==1) {
					kb.deadWumpus();
				}
				break;
			default:
				//f.setLiteral(new Literal(getPosition(),LiteralType.EMPTY), 0);
				break;
			}
		}
		kb.addRule(f);
//		kb.print();
//		System.out.println("------------------------");
		//kb.removeLiteral(l);
	}
	
	public void tellKB(Action a) {
		//TODO
	}
	
	private boolean askKB(Action a) {
		Point pos = new Point(getPosition());
		FieldValues dir = getDirection();
		boolean result = true;
		boolean tmp = false;
		DefaultMutableTreeNode rootNode;
		
		switch (a) {
		case MOVE:
			move();
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			result = kb.backInference(new Literal(getPosition(), LiteralType.NO_WUMPUS), rootNode);
			rootNode.setUserObject("No Wumpus " + a.toString() + " = [" + result +"]");
			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			tmp = kb.backInference(new Literal(getPosition(), LiteralType.NO_PIT), rootNode);
			rootNode.setUserObject("No Pit " + a.toString() + " = [" + tmp +"]");
			result = result && tmp; 
			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			tmp =  !kb.backInference(new Literal(getPosition(), LiteralType.WALL), rootNode);
			rootNode.setUserObject("Wall " + a.toString() + " = [" + !tmp +"]");
			result = result && tmp;
			kb.clearTemp();
			setPosition(pos);
			return result;
		case TURN_LEFT:
			turn(a);
			move();
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			result = kb.backInference(new Literal(getPosition(), LiteralType.NO_WUMPUS), rootNode);
			rootNode.setUserObject("No Wumpus " + a.toString() + " = [" + result +"]");
			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			tmp = kb.backInference(new Literal(getPosition(), LiteralType.NO_PIT), rootNode);
			rootNode.setUserObject("No Pit " + a.toString() + " = [" + tmp +"]");
			result = result && tmp; 
			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			tmp = !kb.backInference(new Literal(getPosition(), LiteralType.WALL), rootNode);
			rootNode.setUserObject("Wall " + a.toString() + " = [" + !tmp +"]");
			result = result && tmp;
			kb.clearTemp();
			setPosition(pos);
			setDirection(dir);
			return result;
		case TURN_RIGHT:
			turn(a);
			move();
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			result = kb.backInference(new Literal(getPosition(), LiteralType.NO_WUMPUS), rootNode);
			rootNode.setUserObject("No Wumpus " + a.toString() + " = [" + result +"]");
			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			tmp = kb.backInference(new Literal(getPosition(), LiteralType.NO_PIT), rootNode);
			rootNode.setUserObject("No Pit " + a.toString() + " = [" + tmp +"]");
			result = result && tmp; 
			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode("Wall TURN_RIGHT");
			tmp = !kb.backInference(new Literal(getPosition(), LiteralType.WALL), rootNode);
			rootNode.setUserObject("Wall " + a.toString() + " = [" + !tmp +"]");
			result = result && tmp;
			kb.clearTemp();
			setPosition(pos);
			setDirection(dir);
			return result;
		case GRAB:
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			result = kb.backInference(new Literal(getPosition(), LiteralType.GOLD), rootNode);
			rootNode.setUserObject("GOLD " + a.toString() + " = [" + result +"]");
			return result;
		case SHOOT:
			move();
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			result = kb.backInference(new Literal(getPosition(), LiteralType.WUMPUS), rootNode);
			rootNode.setUserObject("WUMPUS " + a.toString() + " = [" + result +"]");
			kb.clearTemp();
			setPosition(pos);
			return result;
		case COVER:
			move();
//			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
//			result = kb.backInference(new Literal(getPosition(), LiteralType.PIT), rootNode);
//			rootNode.setUserObject("Pit " + a.toString() + " = [" + result +"]");
//			kb.clearTemp();
			
			rootNode = Main.getInstance().getTree().addRootNode(a.toString());
			tmp = !kb.backInference(new Literal(getPosition(), LiteralType.NO_PIT), rootNode);
			rootNode.setUserObject("No Pit " + a.toString() + " = [" + !tmp +"]");
			result = result && tmp;
			kb.clearTemp();
			setPosition(pos);
			return result;
		default:
			return false;
		}		
	}
	

	//To trzeba znacznei rozszerzyc
	public Action chooseAndPerformAction() {
		Action a = Action.TURN_LEFT;
		
		// ustaw obeny węzeł na odwiedzony
		currentNode.setVisited();
		visitedNodes.add(currentNode.getPosition());
		
		// sprawdź czy złoto
		if (askKB(Action.GRAB)) {
			return Action.GRAB;
		}
		
		// sprawdź czy jest wumpus
		if ((getArrow()>0) && askKB(Action.SHOOT)) {
			return Action.SHOOT;
		}
		
		// sprawdź czy zasypać dziure
		if ((getSandbag()>0) && coverHole && askKB(Action.COVER)) {
			coverHole = false;
			kb.coveredHole(getPosition(),getDirection());
			return Action.COVER;
		}
		
		// znajdź nowe węzły dzieci
		if (askKB(Action.MOVE)) {
			Point pos = getForwardPosition();
			if (!visitedNodes.contains(pos) && !currentNode.containsChildNode(pos) && !allPossibleNodes.contains(pos)) {
				DFSTreeNode forward = new DFSTreeNode(pos);
				if (isInsideField(pos))
					currentNode.add(forward);
				addPossibleNode(pos);
			}
		} 
		if (askKB(Action.TURN_RIGHT)) {
			Point pos = getRightPosition();
			if (!visitedNodes.contains(pos) && !currentNode.containsChildNode(pos) && !allPossibleNodes.contains(pos)) {
				DFSTreeNode onTheRight = new DFSTreeNode(pos);
				if (isInsideField(pos))
					currentNode.add(onTheRight);
				addPossibleNode(pos);
			}
		} 
		if (askKB(Action.TURN_LEFT)) {
			Point pos = getLeftPosition();
			if (!visitedNodes.contains(pos) && !currentNode.containsChildNode(pos) && !allPossibleNodes.contains(pos)) {
				DFSTreeNode onTheLeft = new DFSTreeNode(pos);
				if (isInsideField(pos))
					currentNode.add(onTheLeft);
				addPossibleNode(pos);
			}
		}		
		
		//idź do pierwszego nieodwiedzonego dziecka
		DFSTreeNode c = currentNode.getFirstNotVisitedChild(); 
		if (c == null) {		// jeśli nie ma nieodwiedzonego dziecka to wróć do rodzica
			c = (DFSTreeNode) currentNode.getParent();
			// sprawdź czy wszstykie możliwe węzły odwiedzone
			if (DFSTreeNode.areAllPossibleChildrenVisited(rootNode)) {
				System.out.println("Wszystkie :-)");
				findAndCoverHole();
			}
		}
		
		a = goToNode(c);
		if (a == Action.MOVE) {
			currentNode = c;
		}
		
		return a;
	}
	
	
	private void addPossibleNode(Point p) {
		if (p.x >= 0 && p.x < Main.getInstance().getMap().getSize() &&
				p.y >= 0 && p.y < Main.getInstance().getMap().getSize()) {
				allPossibleNodes.add(new Point(p));
			}		
	}
	
	
	private void findAndCoverHole() {
		rootNode = currentNode = new DFSTreeNode(getPosition(), true);
		DFSTree = new JTree(currentNode);
		allPossibleNodes.clear();
		allPossibleNodes.add(new Point(getPosition()));
		visitedNodes.clear();
		visitedNodes.add(getPosition());
		coverHole = true;
	}
	
	
	private boolean isInsideField(Point p) {
		if (p.x >= 0 && p.x < Main.getInstance().getMap().getSize() &&
				p.y >= 0 && p.y < Main.getInstance().getMap().getSize()) {
				return true;
			} else {
				return false;
			}
	}
	
	
	private Point getForwardPosition() {
		switch (dir) {
		case UP:
			return new Point(position.x, position.y-1);
		case RIGHT:
			return new Point(position.x+1, position.y);
		case DOWN:
			return new Point(position.x, position.y+1);
		case LEFT:
			return new Point(position.x-1, position.y);

		default:
			System.out.println("getForwardPosition: wrong direction!");
			return new Point(position);
		}
	}
	
	private Point getLeftPosition() {
		switch (dir) {
		case UP:
			return new Point(position.x-1, position.y);
		case RIGHT:
			return new Point(position.x, position.y-1);
		case DOWN:
			return new Point(position.x+1, position.y);
		case LEFT:
			return new Point(position.x, position.y+1);

		default:
			System.out.println("getLeftPosition: wrong direction!");
			return new Point(position);
		}
	}
	
	private Point getRightPosition() {
		switch (dir) {
		case UP:
			return new Point(position.x+1, position.y);
		case RIGHT:
			return new Point(position.x, position.y+1);
		case DOWN:
			return new Point(position.x-1, position.y);
		case LEFT:
			return new Point(position.x, position.y-1);

		default:
			System.out.println("getRightPosition: wrong direction!");
			return new Point(position);
		}
	}
	
	/**
	 * Zwraca pierwszą akację jaką trzeba wykonać, żeby dojść na daną pozycję.
	 * @param p
	 * @return
	 */
	private Action goToNode(DFSTreeNode node) {
		Point p = node.getPosition();
		
		if (position.equals(p)) {
			System.out.println("goToNode: idę do pozycji w której aktualnie jestem!");
			return Action.TURN_LEFT;
		}
		
		if (p.x < position.x) {		// idziemy w lewo
			if (dir == FieldValues.LEFT) {
				return Action.MOVE;
			} else {
				return Action.TURN_LEFT;
			}
		}
		
		if (p.y < position.y) {		// idziemy w górę
			if (dir == FieldValues.UP) {
				return Action.MOVE;
			} else {
				return Action.TURN_LEFT;
			}
		}
		
		if (p.x > position.x) {		// idziemy w prawo
			if (dir == FieldValues.RIGHT) {
				return Action.MOVE;
			} else {
				return Action.TURN_RIGHT;
			}
		}
									// idziemy w dół
		if (dir == FieldValues.DOWN) {
			return Action.MOVE;
		} else {
			return Action.TURN_RIGHT;
		}
	}
	
	
}
